package annotation;

import org.openqa.selenium.WebElement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.context.ApplicationContext;
import tools.AnnotationUtil;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Optional;

public class PageEntityProxy<T extends IProxyable<T>,E extends IProxyable<E>> implements MethodInterceptor {
    private final T targetPage;
    private final E targetEntity;

    private ApplicationContext applicationContext;

    static Logger logger = LoggerFactory.getLogger(PageEntityProxy.class);

    public PageEntityProxy(T targetPage, E targetEntity, ApplicationContext applicationContext) {
        this.targetPage = targetPage;
        this.targetEntity = targetEntity;
        this.applicationContext =applicationContext;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {

        if (targetEntity instanceof IProxyable) {
            if (method.getName().equals("getTarget") && method.getParameterCount() == 0) {
                return targetEntity;
            }
        }

        Class<?> tClass = AopUtils.getTargetClass(targetEntity);
        PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(tClass);
        for (PropertyDescriptor pd : pds) {
            if (method.equals(pd.getReadMethod())) {
                return interceptGetter(tClass,pd, method);
            }
            if (method.equals(pd.getWriteMethod())){
                return interceptSetter(tClass,pd, method,args);
            }
        }

        return method.invoke(targetEntity, args);
    }

    private Object interceptGetter(Class<?> tClass, PropertyDescriptor propertyDescriptor, Method method) throws NoSuchFieldException, InvocationTargetException, IllegalAccessException, InstantiationException {
        Class<?> viewClass = targetPage.getTarget().getClass();
        Field field = tClass.getDeclaredField(propertyDescriptor.getName());
        if (field.isAnnotationPresent(BindGetter.class)) {
            BindGetter bindGetter = field.getAnnotation(BindGetter.class);
            Class<? extends IPropertityGetter> getterClass = bindGetter.getter();
            IPropertityGetter propertityGetter = applicationContext.getBean(getterClass);
            String mapperFiledName = bindGetter.name();
            if (mapperFiledName.isEmpty()){
                mapperFiledName = field.getName();
            }

            // 在targetPage中找相应的字段
            Field viewField = null;
            PropertyDescriptor viewProperty = null;
            PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(viewClass);
            for (PropertyDescriptor pd : pds) {
                if (pd.getName().equals(mapperFiledName)) {
                    viewField = viewClass.getDeclaredField(mapperFiledName);
                    viewProperty = pd;
                }
            }

            if (viewField!=null){
                // 通过propertityGetter处理get结果
                Object viewObject = viewProperty.getReadMethod().invoke(targetPage);
                if (viewObject instanceof Optional){
                    viewObject = ((Optional) viewObject).orElse(null);
                }
                if (viewObject instanceof WebElement){
                    Args addtionArgs = AnnotationUtil.getAnnotation(field, Args.class).orElse(null);
                    Object result = propertityGetter.getPropertity(viewObject,targetPage,addtionArgs);
                    field.setAccessible(true);
                    field.set(targetEntity, result);
                    return result;
                }else {
                    logger.warn("The field "+ mapperFiledName +" in the page class "+ viewClass.getName()+" should be WebElement or Optional<WebElement> type.");
                }
            }
            else {
                logger.warn("Cannot find field "+ mapperFiledName +" in the page class "+ viewClass.getName());
            }
        }
        return method.invoke(targetEntity);
    }

    private Object interceptSetter(Class<?> tClass, PropertyDescriptor propertyDescriptor, Method method, Object[] args) throws NoSuchFieldException, InvocationTargetException, IllegalAccessException {
        Class<?> viewClass = targetPage.getTarget().getClass();
        Field field = tClass.getDeclaredField(propertyDescriptor.getName());
        if (field.isAnnotationPresent(BindSetter.class)) {
            BindSetter bindSetter = field.getAnnotation(BindSetter.class);
            Class<? extends IPropertitySetter> setterClass = bindSetter.setter();
            IPropertitySetter propertitySetter = applicationContext.getBean(setterClass);
            String mapperFiledName = bindSetter.name();
            if (mapperFiledName.isEmpty()){
                mapperFiledName = field.getName();
            }

            // 在targetPage中找相应的字段
            Field viewField = null;
            PropertyDescriptor viewProperty = null;
            PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(viewClass);
            for (PropertyDescriptor pd : pds) {
                if (pd.getName().equals(mapperFiledName)) {
                    viewField = viewClass.getDeclaredField(mapperFiledName);
                    viewProperty = pd;
                }
            }

            if (viewField!=null){
                // 通过propertitySetter处理get结果
                Object viewObject = viewProperty.getReadMethod().invoke(targetPage);
                if (viewObject instanceof Optional){
                    viewObject = ((Optional) viewObject).orElse(null);
                }
                if (viewObject instanceof WebElement){
                    field.setAccessible(true);
                    field.set(targetEntity, args[0]);
                    Args addtionArgs = AnnotationUtil.getAnnotation(field, Args.class).orElse(null);
                    propertitySetter.setPropertity(viewObject,args[0],targetPage,addtionArgs);
                    return null;
                }else {
                    logger.warn("The field "+ mapperFiledName +" in the page class "+ viewClass.getName()+" should be WebElement or Optional<WebElement> type.");
                }
            }
            else {
                logger.warn("Cannot find field "+ mapperFiledName +" in the page class "+ viewClass.getName());
            }
        }
        return method.invoke(targetEntity,args);
    }

}
